package com.cloudkinto.service.order.outbound.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.ListUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.cloudkinto.bo.order.allocate.AllocateCancelAddBo;
import com.cloudkinto.bo.order.allocate.AllocateCancelQueryBo;
import com.cloudkinto.bo.order.allocate.BackAllocateShelfBo;
import com.cloudkinto.bo.order.outbound.BackOutboundShelfBo;
import com.cloudkinto.bo.order.outbound.OutboundCancelAddBo;
import com.cloudkinto.bo.order.outbound.OutboundCancelQueryBo;
import com.cloudkinto.bo.storage.StorageLocationQueryBo;
import com.cloudkinto.common.BeanConvert;
import com.cloudkinto.common.RabbitQueueConstant;
import com.cloudkinto.common.RedisKeyConstants;
import com.cloudkinto.common.common.DateUtils;
import com.cloudkinto.common.constant.SysConstant;
import com.cloudkinto.common.exception.BizException;
import com.cloudkinto.common.utils.StringsUtil;
import com.cloudkinto.common.web.PageResult;
import com.cloudkinto.common.web.SingleResult;
import com.cloudkinto.dao.AllocatePlanDao;
import com.cloudkinto.dao.OrderOutboundDao;
import com.cloudkinto.dao.OutboundCancelDao;
import com.cloudkinto.dao.OutboundWaveDao;
import com.cloudkinto.entity.*;
import com.cloudkinto.enumType.common.ProgressEnum;
import com.cloudkinto.enumType.operate.OperateTypeEnum;
import com.cloudkinto.enumType.order.allocate.AllocatePlanStatus;
import com.cloudkinto.enumType.order.outbound.*;
import com.cloudkinto.enumType.stock.StockOrderTypeEnum;
import com.cloudkinto.enumType.stock.StockTypeEnum;
import com.cloudkinto.service.channel.LogisticsChannelService;
import com.cloudkinto.service.company.CompanyService;
import com.cloudkinto.service.consumer.vo.order.PlatOrderCancelConsumerReq;
import com.cloudkinto.service.consumer.vo.order.PlatformOrderCancelReq;
import com.cloudkinto.service.operate.OperateRecordService;
import com.cloudkinto.service.order.allocate.AllocatePlanItemService;
import com.cloudkinto.service.order.box.OrderBoxService;
import com.cloudkinto.service.order.outbound.OrderOutboundProductService;
import com.cloudkinto.service.order.outbound.OutboundCancelService;
import com.cloudkinto.service.order.picking.OutboundPickingService;
import com.cloudkinto.service.product.ProductService;
import com.cloudkinto.service.progress.ProgressService;
import com.cloudkinto.service.stock.ProductStockFlowingService;
import com.cloudkinto.service.stock.ProductStockRackService;
import com.cloudkinto.service.storage.StorageLocationService;
import com.cloudkinto.service.warehouse.WarehouseService;
import com.cloudkinto.vo.common.CommonBatchOperationVo;
import com.cloudkinto.vo.order.allocate.AllocateCancelVo;
import com.cloudkinto.vo.order.allocate.BackAllocateShelfVo;
import com.cloudkinto.vo.order.outbound.BackOutboundShelfVo;
import com.cloudkinto.vo.order.outbound.OutboundCancelVo;
import com.cloudkinto.vo.stock.ProductFlowingInEt;
import com.cloudkinto.vo.storage.StorageLocationVo;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import lombok.RequiredArgsConstructor;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionTemplate;

import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;

/**
 * <p>
 * 出库单截单 service实现类
 * </p>
 *
 * @author long
 * @since 2024-12-09
 */
@Service
@Transactional(rollbackFor = Exception.class)
@RequiredArgsConstructor
public class OutboundCancelServiceImpl extends ServiceImpl<OutboundCancelDao, OutboundCancelDo> implements OutboundCancelService {

    private final CompanyService companyService;
    private final LogisticsChannelService logisticsChannelService;
    private final RedisTemplate redisTemplate;
    private final OrderOutboundDao orderOutboundDao;
    private final OperateRecordService operateRecordService;
    private final OrderOutboundProductService outboundProductService;
    private final OutboundPickingService pickingService;
    private final ProductService productService;
    private final OrderBoxService boxService;
    private final StorageLocationService storageLocationService;
    private final ProgressService progressService;
    private final ProductStockRackService productStockRackService;
    private final ProductStockFlowingService stockFlowingService;
    private final RabbitTemplate rabbitTemplate;
    private final OutboundWaveDao waveDao;
    private final AllocatePlanDao allocatePlanDao;
    private final AllocatePlanItemService allocatePlanItemService;
    private final TransactionTemplate transactionTemplate;
    private final ThreadPoolExecutor threadPoolExecutor;
    private final WarehouseService warehouseService;

    /**
     * 用于stream流的filter方法做实体类的某个属性去重
     */
    static <T> Predicate<T> distinctByKey(Function<? super T, ?> keyExtractor) {
        Map<Object, Boolean> seen = new ConcurrentHashMap<>();
        //putIfAbsent方法添加键值对，如果map集合中没有该key对应的值，则直接添加，并返回null，如果已经存在对应的值，则依旧为原来的值。
        //如果返回null表示添加数据成功(不重复)，不重复(null==null :TRUE)
        return t -> seen.putIfAbsent(keyExtractor.apply(t), Boolean.TRUE) == null;
    }

    public void add(PlatformOrderCancelReq req) {
        List<OrderOutboundDo> outboundDoList = orderOutboundDao.selectList(Wrappers.<OrderOutboundDo>lambdaQuery().select(OrderOutboundDo::getId)
                .in(OrderOutboundDo::getPlatformOrderNo, req.getPlatformOrderNos()).eq(OrderOutboundDo::getStoreId, req.getStoreId())
                .notIn(OrderOutboundDo::getStatus, NormalOutboundStatusEnum.five.getValue(), NormalOutboundStatusEnum.seven.getValue())
                .ge(OrderOutboundDo::getCreateTime, DateUtils.dateFormatToString(DateUtils.passingDate(new Date(), -30)))
                .eq(OrderOutboundDo::getOrderOrigin, OutboundOriginEnum.three.getValue()));
        if (outboundDoList.isEmpty()) {
            return;
        }
        List<Long> outIds = outboundDoList.stream().map(OrderOutboundDo::getId).collect(Collectors.toList());
        OutboundCancelAddBo addBo = new OutboundCancelAddBo(outIds);
        try {
            this.add(addBo, null, null);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Override
    public CommonBatchOperationVo add(OutboundCancelAddBo req, Long userId, String username) {
        List<OrderOutboundDo> outboundDoList = orderOutboundDao.selectList(Wrappers.<OrderOutboundDo>lambdaQuery().in(OrderOutboundDo::getId, req.getOutboundIdList()));
        if (CollUtil.isEmpty(outboundDoList)) {
            throw new BizException(SysConstant.Outbound_Order_Does_Not_Exist);
        }
        Map<Long, List<OrderOutboundProductDo>> outOrderIdMatchProductDoList = outboundProductService.lambdaQuery()
                .in(OrderOutboundProductDo::getOutboundId, outboundDoList.stream().map(OrderOutboundDo::getId).collect(Collectors.toList()))
                .list().stream().collect(Collectors.groupingBy(OrderOutboundProductDo::getOutboundId));
        Map<Long, String> whIdMatchWhCode = warehouseService.lambdaQuery()
                .in(WarehouseDo::getId, outboundDoList.stream().map(OrderOutboundDo::getWhId).collect(Collectors.toSet()))
                .list().stream().collect(Collectors.toMap(WarehouseDo::getId, WarehouseDo::getWhCode));
        CommonBatchOperationVo vo = new CommonBatchOperationVo();
        vo.setTotal(outboundDoList.size());
        List<OutboundCancelDo> outboundCancelDoList = new ArrayList<>(outboundDoList.size());
        for (OrderOutboundDo outboundDo : outboundDoList) {
            if ((Objects.equals(outboundDo.getOrderType(), OutboundTypeEnum.one.getValue()) && (NormalOutboundStatusEnum.five.getValue().equals(outboundDo.getStatus()) || NormalOutboundStatusEnum.seven.getValue().equals(outboundDo.getStatus()))) || (Objects.equals(outboundDo.getOrderType(), OutboundTypeEnum.two.getValue()) && (TransferOutboundStatusEnum.four.getValue().equals(outboundDo.getStatus()) || TransferOutboundStatusEnum.six.getValue().equals(outboundDo.getStatus())))) {
                vo.setFailed(vo.getFailed() + 1);
                HashMap<String, Object> map = new HashMap<>(4);
                map.put("code", outboundDo.getOutboundNo());
                map.put("errorMsg", StringsUtil.createI18Message(SysConstant.Outbound_Status_Cannot_Cancel));
                vo.getFailedList().add(map);
                continue;
            }
            if (Objects.nonNull(outboundDo.getMarkExceptionTime())) {
                vo.setFailed(vo.getFailed() + 1);
                HashMap<String, Object> map = new HashMap<>(4);
                map.put("code", outboundDo.getOutboundNo());
                map.put("errorMsg", StringsUtil.createI18Message(SysConstant.Marked_Exception_Cancel_Fail));
                vo.getFailedList().add(map);
                continue;
            }
            OutboundCancelDo one = this.getOne(Wrappers.<OutboundCancelDo>lambdaQuery()
                    .eq(OutboundCancelDo::getOrderKind, OutboundOrderKind.outbound.getValue())
                    .eq(OutboundCancelDo::getOutboundId, outboundDo.getId())
                    .orderByDesc(OutboundCancelDo::getCreateTime)
                    .last("limit 1")
            );
            if (Objects.nonNull(one)) {
                if (Objects.equals(one.getStatus(), 0)) {
                    vo.setFailed(vo.getFailed() + 1);
                    HashMap<String, Object> map = new HashMap<>(4);
                    map.put("code", outboundDo.getOutboundNo());
                    map.put("errorMsg", StringsUtil.createI18Message(SysConstant.Processing_Cancel_Fail));
                    vo.getFailedList().add(map);
                    continue;
                } else if (Objects.equals(one.getStatus(), 1)) {
                    vo.setFailed(vo.getFailed() + 1);
                    HashMap<String, Object> map = new HashMap<>(4);
                    map.put("code", outboundDo.getOutboundNo());
                    map.put("errorMsg", StringsUtil.createI18Message(SysConstant.Finish_Cancel_Fail));
                    vo.getFailedList().add(map);
                    continue;
                }
            }
            //修改出库单拦截状态
            outboundDo.setInterceptionFlag(Boolean.TRUE);
            orderOutboundDao.updateById(outboundDo);
            operateRecordService.operateAddRecordAndValue(
                    SysConstant.Order_Start_Interception,
                    "",
                    outboundDo.getId(),
                    userId,
                    username,
                    OperateTypeEnum.OrderOutbound
            );
            OutboundCancelDo entityDo = new OutboundCancelDo();
            entityDo.setOrderKind(OutboundOrderKind.outbound.getValue());
            entityDo.setTenantId(outboundDo.getTenantId());
            //出库单状态为待处理或京东仓，自动截单成功
            if (outboundDo.getStatus().equals(1) || "JD01".equals(whIdMatchWhCode.get(outboundDo.getWhId()))) {
                //如存在锁定库存需要释放
                List<OrderOutboundProductDo> outboundProductDoList = outOrderIdMatchProductDoList.get(outboundDo.getId());
                outboundProductService.lambdaUpdate()
                        .eq(OrderOutboundProductDo::getOutboundId, outboundDo.getId())
                        .set(OrderOutboundProductDo::getOutboundQty, 0)
                        .set(OrderOutboundProductDo::getOutboundBoxQty, 0)
                        .update();
                List<OutboundPickingDo> pickingDoList = pickingService.lambdaQuery()
                        .eq(OutboundPickingDo::getOrderType, OutboundOrderKind.outbound.getValue())
                        .in(OutboundPickingDo::getOutboundProductId, outboundProductDoList.stream().map(OrderOutboundProductDo::getId).collect(Collectors.toList()))
                        .list();
                if (CollUtil.isNotEmpty(pickingDoList)) {
                    this.unLock(
                            JSONUtil.toList(JSONUtil.toJsonStr(pickingDoList), OutboundPickingDo.class),
                            pickingDoList,
                            new AtomicInteger(pickingDoList.stream().map(OutboundPickingDo::getQty).reduce(0, Integer::sum))
                    );
                }
                entityDo.setStatus(1);
                entityDo.setFinishTime(DateUtil.offsetMinute(DateUtil.date(), 1));
                outboundDo.setStatus(Objects.equals(outboundDo.getOrderType(), OutboundTypeEnum.one.getValue()) ? NormalOutboundStatusEnum.seven.getValue() : TransferOutboundStatusEnum.six.getValue());
                outboundDo.setCancelTime(DateUtil.date());
                orderOutboundDao.updateById(outboundDo);
                operateRecordService.operateAddRecordAndValue(
                        SysConstant.Order_Interception_Success,
                        "",
                        outboundDo.getId(),
                        userId,
                        username,
                        OperateTypeEnum.OrderOutbound
                );
            }
            entityDo.setOutboundId(outboundDo.getId());
            entityDo.setBackNo(this.generateCancelOutboundNo());
            outboundCancelDoList.add(entityDo);
            vo.setSuccess(vo.getSuccess() + 1);
        }
        this.saveBatch(outboundCancelDoList);
        return vo;
    }

    @Override
    public CommonBatchOperationVo allocateAdd(AllocateCancelAddBo req, Long userId, String username) {
        List<AllocatePlanDo> allocatePlanDoList = allocatePlanDao.selectList(Wrappers.<AllocatePlanDo>lambdaQuery().in(AllocatePlanDo::getId, req.getAllocatePlanIdList()));
        if (CollUtil.isEmpty(allocatePlanDoList)) {
            throw new BizException(SysConstant.Allocate_Order_Does_Not_Exist);
        }
        Map<Long, List<AllocatePlanItemDo>> outOrderIdMatchProductDoList = allocatePlanItemService.lambdaQuery()
                .in(AllocatePlanItemDo::getOrderId, allocatePlanDoList.stream().map(AllocatePlanDo::getId).collect(Collectors.toList()))
                .list().stream().collect(Collectors.groupingBy(AllocatePlanItemDo::getOrderId));
        CommonBatchOperationVo vo = new CommonBatchOperationVo();
        vo.setTotal(allocatePlanDoList.size());
        List<OutboundCancelDo> outboundCancelDoList = new ArrayList<>(allocatePlanDoList.size());
        for (AllocatePlanDo planDo : allocatePlanDoList) {
            if (Objects.nonNull(planDo.getMarkExceptionTime())) {
                vo.setFailed(vo.getFailed() + 1);
                HashMap<String, Object> map = new HashMap<>(4);
                map.put("code", planDo.getPlanNo());
                map.put("errorMsg", StringsUtil.createI18Message(SysConstant.Marked_Exception_Cancel_Fail));
                vo.getFailedList().add(map);
                continue;
            }
            OutboundCancelDo one = this.getOne(Wrappers.<OutboundCancelDo>lambdaQuery()
                    .eq(OutboundCancelDo::getOrderKind, OutboundOrderKind.allocate.getValue())
                    .eq(OutboundCancelDo::getOutboundId, planDo.getId())
                    .orderByDesc(OutboundCancelDo::getCreateTime)
                    .last("limit 1")
            );
            if (Objects.nonNull(one)) {
                if (one.getStatus().equals(0)) {
                    vo.setFailed(vo.getFailed() + 1);
                    HashMap<String, Object> map = new HashMap<>(4);
                    map.put("code", planDo.getPlanNo());
                    map.put("errorMsg", StringsUtil.createI18Message(SysConstant.Processing_Cancel_Fail));
                    vo.getFailedList().add(map);
                    continue;
                } else if (one.getStatus().equals(1)) {
                    vo.setFailed(vo.getFailed() + 1);
                    HashMap<String, Object> map = new HashMap<>(4);
                    map.put("code", planDo.getPlanNo());
                    map.put("errorMsg", StringsUtil.createI18Message(SysConstant.Finish_Cancel_Fail));
                    vo.getFailedList().add(map);
                    continue;
                }
            }
            //修改调拨发货计划拦截状态
            planDo.setInterceptionFlag(Boolean.TRUE);
            allocatePlanDao.updateById(planDo);
            operateRecordService.operateAddRecordAndValue(
                    SysConstant.Order_Start_Interception,
                    "",
                    planDo.getId(),
                    userId,
                    username,
                    OperateTypeEnum.AllocatePlan
            );
            OutboundCancelDo cancelDo = new OutboundCancelDo();
            cancelDo.setOrderKind(OutboundOrderKind.allocate.getValue());
            //出库单状态为待处理，自动截单成功
            if (AllocatePlanStatus.one.getValue().equals(planDo.getStatus())) {
                //如存在锁定库存需要释放
                List<AllocatePlanItemDo> planItemDoList = outOrderIdMatchProductDoList.get(planDo.getId());
                allocatePlanItemService.lambdaUpdate()
                        .eq(AllocatePlanItemDo::getOrderId, planDo.getId())
                        .set(AllocatePlanItemDo::getActualShipments, 0)
                        .set(AllocatePlanItemDo::getLockQty, 0)
                        .update();
                List<OutboundPickingDo> pickingDoList = pickingService.lambdaQuery()
                        .eq(OutboundPickingDo::getOrderType, OutboundOrderKind.allocate.getValue())
                        .in(OutboundPickingDo::getOutboundProductId, planItemDoList.stream().map(AllocatePlanItemDo::getId).collect(Collectors.toList()))
                        .list();
                if (CollUtil.isNotEmpty(pickingDoList)) {
                    this.unLock(
                            JSONUtil.toList(JSONUtil.toJsonStr(pickingDoList), OutboundPickingDo.class),
                            pickingDoList,
                            new AtomicInteger(pickingDoList.stream().map(OutboundPickingDo::getQty).reduce(0, Integer::sum))
                    );
                }
                cancelDo.setStatus(1);
                cancelDo.setFinishTime(DateUtil.offsetMinute(DateUtil.date(), 1));
                planDo.setStatus(AllocatePlanStatus.six.getValue());
                planDo.setCancelTime(DateUtil.date());
                allocatePlanDao.updateById(planDo);
                operateRecordService.operateAddRecordAndValue(
                        SysConstant.Order_Interception_Success,
                        "",
                        planDo.getId(),
                        userId,
                        username,
                        OperateTypeEnum.AllocatePlan
                );
            }
            cancelDo.setOutboundId(planDo.getId());
            cancelDo.setBackNo(this.generateCancelOutboundNo());
            outboundCancelDoList.add(cancelDo);
            vo.setSuccess(vo.getSuccess() + 1);
        }
        this.saveBatch(outboundCancelDoList);
        return vo;
    }

    public void unLock(List<OutboundPickingDo> unLockPickingList, List<OutboundPickingDo> noPickingDoList, AtomicInteger needUnlock) {
        unLockPickingList.stream()
                .sorted(Comparator.comparing(OutboundPickingDo::getSubStockId))
                .forEach(item -> {
                    if (needUnlock.get() == 0) {
                        //跳过档次循环
                        return;
                    }
                    if (item.getQty() <= needUnlock.get()) {
                        productStockRackService.lambdaUpdate()
                                .eq(ProductStockRackDo::getId, item.getSubStockId())
                                .setSql("quantity = quantity + " + item.getQty())
                                .setSql("lock_quantity = lock_quantity - " + item.getQty())
                                .update();
                        pickingService.removeById(item.getId());
                        noPickingDoList.forEach(noPickingDo -> {
                            if (noPickingDo.getId().equals(item.getId())) {
                                noPickingDo.setPickFlag(null);
                            }
                        });
                        needUnlock.set(needUnlock.get() - item.getQty());
                    } else {
                        productStockRackService.lambdaUpdate()
                                .eq(ProductStockRackDo::getId, item.getSubStockId())
                                .setSql("quantity = quantity + " + needUnlock.get())
                                .setSql("lock_quantity = lock_quantity - " + item.getQty())
                                .update();
                        pickingService.lambdaUpdate()
                                .eq(OutboundPickingDo::getId, item.getId())
                                .setSql("qty = qty -" + needUnlock.get())
                                .update();
                        //修改待拣货列表数据
                        noPickingDoList.forEach(noPickingDo -> {
                            if (noPickingDo.getId().equals(item.getId())) {
                                noPickingDo.setQty(noPickingDo.getQty() - needUnlock.get());
                            }
                        });
                        needUnlock.set(0);
                    }
                });
    }

    /**
     * 生成截单号
     */
    private String generateCancelOutboundNo() {
        // 每周重置截单序号
        //生成入库单号
        String orderInputNoSeq = String.valueOf(redisTemplate.opsForValue().increment(RedisKeyConstants.CANCEL_OUTBOUND_NO_SEQ));
        //序列号不足六位在前面补0
        if (orderInputNoSeq.length() < 6) {
            orderInputNoSeq = "000000".substring(0, 6 - orderInputNoSeq.length()) + orderInputNoSeq;
        }
        return "BO"
               + DateUtil.format(DateUtil.date(), "yyMMdd")
               + orderInputNoSeq;
    }

    @Override
    public SingleResult pageInit(Long userId, Long tenantId) {
        HashMap<String, Object> map = new HashMap<>(8);
        map.put("companyList", companyService.getCompanyList(userId, tenantId, "wms"));
        map.put("logisticsChannelList", logisticsChannelService.lambdaQuery().eq(LogisticsChannelDo::getTenantId, tenantId).list());
        map.put("normalStatusList", NormalOutboundStatusEnum.getList());
        map.put("transferStatusList", TransferOutboundStatusEnum.getList());
        map.put("allocateStatusList", AllocatePlanStatus.getAllocatePlanStatusList());
        return SingleResult.success(map);
    }

    @Override
    public PageResult getListPage(OutboundCancelQueryBo bo) {
        Integer current = bo.getCurrent();
        Integer pageSize = bo.getPageSize();
        PageHelper.startPage(current, pageSize);
        List<OutboundCancelVo> list = baseMapper.getListPage(bo);
        PageInfo<OutboundCancelVo> pageInfo = new PageInfo<>(list);
        return new PageResult<>(
                list,
                current,
                pageSize,
                (int) pageInfo.getTotal(),
                pageInfo.getPages()
        );
    }

    @Override
    public PageResult getAllocateListPage(AllocateCancelQueryBo bo) {
        Integer current = bo.getCurrent();
        Integer pageSize = bo.getPageSize();
        PageHelper.startPage(current, pageSize);
        List<AllocateCancelVo> list = baseMapper.getAllocateListPage(bo);
        PageInfo<AllocateCancelVo> pageInfo = new PageInfo<>(list);
        return new PageResult<>(
                list,
                current,
                pageSize,
                (int) pageInfo.getTotal(),
                pageInfo.getPages()
        );
    }

    @Override
    public SingleResult markFail(Long id, Long userId, String username) {
        OutboundCancelDo cancelDo = this.getById(id);
        if (Objects.isNull(cancelDo)) {
            throw new BizException(SysConstant.ORDER_CANCEL_NOT_EXIST);
        }
        OrderOutboundDo outboundDo = orderOutboundDao.selectById(cancelDo.getOutboundId());
        //修改出库单拦截状态及日志
        outboundDo.setInterceptionFlag(Boolean.FALSE);
        orderOutboundDao.updateById(outboundDo);
        operateRecordService.operateAddRecordAndValue(
                SysConstant.Order_Interception_Fail,
                "",
                outboundDo.getId(),
                userId,
                username,
                OperateTypeEnum.OrderOutbound
        );
        return SingleResult.success(this.lambdaUpdate()
                .eq(OutboundCancelDo::getId, id)
                .set(OutboundCancelDo::getStatus, 2)
                .set(OutboundCancelDo::getFinishTime, DateUtil.date())
                .update());
    }

    @Override
    public SingleResult allocateMarkFail(Long id, Long userId, String username) {
        OutboundCancelDo cancelDo = this.getById(id);
        if (Objects.isNull(cancelDo)) {
            throw new BizException(SysConstant.ORDER_CANCEL_NOT_EXIST);
        }
        AllocatePlanDo planDo = allocatePlanDao.selectById(cancelDo.getOutboundId());
        //修改调拨拦截状态及日志
        planDo.setInterceptionFlag(Boolean.FALSE);
        allocatePlanDao.updateById(planDo);
        operateRecordService.operateAddRecordAndValue(
                SysConstant.Order_Interception_Fail,
                "",
                planDo.getId(),
                userId,
                username,
                OperateTypeEnum.AllocatePlan
        );
        return SingleResult.success(this.lambdaUpdate()
                .eq(OutboundCancelDo::getId, id)
                .set(OutboundCancelDo::getStatus, 2)
                .set(OutboundCancelDo::getFinishTime, DateUtil.date())
                .update());
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public SingleResult backOutboundShelf(BackOutboundShelfBo bo, Long userId, String username) {
        Long cancelId = bo.getId();
        OutboundCancelDo cancelDo = this.getById(cancelId);
        if (Objects.isNull(cancelDo)) {
            throw new BizException(SysConstant.ORDER_CANCEL_NOT_EXIST);
        }
        if (cancelDo.getStatus() != 0) {
            throw new BizException(SysConstant.BACK_ORDER_SHELF_STATUS_EXCEPTION);
        }
        OrderOutboundDo outboundDo = orderOutboundDao.selectById(bo.getOutboundId());
        if (Objects.isNull(outboundDo)) {
            throw new BizException(SysConstant.Outbound_Order_Does_Not_Exist);
        }
        operateRecordService.operateAddRecordAndValue(
                SysConstant.BACK_ORDER_SHELF,
                "",
                outboundDo.getId(),
                userId,
                username,
                OperateTypeEnum.OrderOutbound
        );
        List<OrderOutboundProductDo> outboundProductDoList = outboundProductService.lambdaQuery()
                .eq(OrderOutboundProductDo::getOutboundId, outboundDo.getId())
                .list();
        List<Long> outboundProductIdList = outboundProductDoList.stream().map(OrderOutboundProductDo::getId).collect(Collectors.toList());
        //查询拣货信息
        List<OutboundPickingDo> pickingDoList = pickingService.lambdaQuery()
                .eq(OutboundPickingDo::getOrderType, OutboundOrderKind.outbound.getValue())
                .in(OutboundPickingDo::getOutboundProductId, outboundProductIdList)
                .list();
        //查询库位信息 用于匹配库存属性
        StorageLocationQueryBo slBo = new StorageLocationQueryBo();
        slBo.setTenantId(outboundDo.getTenantId());
        slBo.setPageSize(1000);
        Map<Long, StorageLocationVo> cellIdMatchDo = storageLocationService.getListPage(slBo).getData()
                .stream().collect(Collectors.toMap(StorageLocationVo::getId, val -> val));
        Map<Long, ProductDo> productIdMatch = productService.lambdaQuery()
                .in(ProductDo::getId, outboundProductDoList.stream().map(OrderOutboundProductDo::getProductId).collect(Collectors.toList()))
                .list().stream().collect(Collectors.toMap(ProductDo::getId, val -> val));
        if (CollUtil.isNotEmpty(bo.getDetailList())) {
            //校验上架数量
            boolean qtyNoMatch = bo.getDetailList().stream()
                    .anyMatch(item -> !item.getShouldShelfQty().equals(item.getCellDetailList().stream()
                            .map(BackOutboundShelfBo.ShelfCellItem::getQty).reduce(0, Integer::sum)));
            if (qtyNoMatch) {
                throw new BizException(SysConstant.Shelf_Qty_No_Match);
            }
            //批量查询库存库位信息
            Map<Long, ProductStockRackDo> stockRackDoMap = productStockRackService.lambdaQuery()
                    .in(ProductStockRackDo::getId, pickingDoList.stream().map(OutboundPickingDo::getSubStockId).collect(Collectors.toSet()))
                    .list().stream().collect(Collectors.toMap(ProductStockRackDo::getId, val -> val));
            //拣货部分 将锁定的库存释放，复制库存子表记录做上架，保证库龄不改变
            //完成拣货的数据进行上架
            for (BackOutboundShelfBo.BackShelfItem shelfItem : bo.getDetailList()) {
                //按产品id或箱类号分组
                List<Long> outboundProductIdListByGroup = outboundProductDoList.stream()
                        .filter(item -> Objects.equals(item.getProductId(), shelfItem.getProductId())
                                        || Objects.equals(item.getBoxTypeNumber(), shelfItem.getSkuOrBoxTypeNumber()))
                        .map(OrderOutboundProductDo::getId)
                        .collect(Collectors.toList());
                //按sku或箱类号分组
                List<OutboundPickingDo> pickingDoListByGroup = pickingDoList.stream()
                        .filter(item -> item.getPickFlag() && outboundProductIdListByGroup.contains(item.getOutboundProductId())).sorted(Comparator.comparing(OutboundPickingDo::getSubStockId))
                        .collect(Collectors.toList());
                for (int i = 0, j = 0; i < shelfItem.getCellDetailList().size(); i++) {
                    BackOutboundShelfBo.ShelfCellItem cellItem = shelfItem.getCellDetailList().get(i);
                    //需要上架数量
                    AtomicInteger needShelfQty = new AtomicInteger(cellItem.getQty());
                    while (j < pickingDoListByGroup.size()) {
                        if (needShelfQty.get() == 0) {
                            break;
                        }
                        OutboundPickingDo pickingDo = pickingDoListByGroup.get(j);
                        //拣货数量
                        Integer pickingQty = pickingDo.getQty();
                        if (pickingQty == 0) {
                            j++;
                            continue;
                        }
                        ProductStockRackDo stockRackDo = stockRackDoMap.get(pickingDo.getSubStockId());
                        int qty;
                        if (pickingQty <= needShelfQty.get()) {
                            j++;
                            qty = pickingQty;
                            pickingDo.setQty(0);
                            needShelfQty.set(needShelfQty.get() - pickingQty);
                            pickingService.removeById(pickingDo.getId());
                        } else {
                            qty = needShelfQty.get();
                            pickingDo.setQty(pickingQty - needShelfQty.get());
                            needShelfQty.set(0);
                        }
                        if (stockRackDo.getReceivedQty().equals(qty)) {
                            //库存数量等于上架数量，直接修改库位上架
                            productStockRackService.lambdaUpdate()
                                    .eq(ProductStockRackDo::getId, pickingDo.getSubStockId())
                                    .setSql("quantity = quantity + " + pickingDo.getQty())
                                    .setSql("lock_quantity = lock_quantity - " + pickingDo.getQty())
                                    .set(ProductStockRackDo::getRackId, cellItem.getRackId())
                                    .set(ProductStockRackDo::getStockAttribute, cellIdMatchDo.get(cellItem.getRackId()).getMainType())
                                    .update();
                        } else {
                            //库存数量大于上架数量，需要复制一条新的库位记录（在创建时间不改变的情况下）
//                            ProductStockRackDo newStockRackDo = new ProductStockRackDo();
//                            BeanUtils.copyProperties(stockRackDo, newStockRackDo);
                            ProductStockRackDo newStockRackDo = BeanConvert.INSTANCE.copyProductStockRackDo(stockRackDo);
                            newStockRackDo.setId(null);
                            newStockRackDo.setReceivedQty(qty);
                            newStockRackDo.setQuantity(qty);
                            newStockRackDo.setLockQuantity(0);
                            newStockRackDo.setRackId(cellItem.getRackId());
                            newStockRackDo.setStockAttribute(cellIdMatchDo.get(cellItem.getRackId()).getMainType());
                            productStockRackService.save(newStockRackDo);
                            productStockRackService.lambdaUpdate()
                                    .eq(ProductStockRackDo::getId, pickingDo.getSubStockId())
                                    .setSql("received_qty = received_qty - " + qty)
                                    .setSql("lock_quantity = lock_quantity - " + pickingDo.getQty())
                                    .set(ProductStockRackDo::getRackId, cellItem.getRackId())
                                    .set(ProductStockRackDo::getStockAttribute, cellIdMatchDo.get(cellItem.getRackId()).getMainType())
                                    .update();
                            stockRackDo.setReceivedQty(stockRackDo.getReceivedQty() - qty);
                        }
                    }
                    //库存流水
                    stockFlowingService.insertProForIn(
                            ProductFlowingInEt.builder()
                                    .sku(
                                            bo.getDataType().equals(1)
                                                    ? productIdMatch.get(shelfItem.getProductId()).getSku()
                                                    : shelfItem.getSkuOrBoxTypeNumber()
                                    )
                                    .companyId(outboundDo.getCompanyId())
                                    .warehouseId(outboundDo.getWhId())
                                    .rackId(cellItem.getRackId())
                                    .stockAttribute(cellIdMatchDo.get(cellItem.getRackId()).getMainType())
                                    .bookQuantity(cellItem.getQty())
                                    .orderType(StockOrderTypeEnum.Cancel.getValue())
                                    .noteNo(cancelDo.getBackNo())
                                    .tenantId(cancelDo.getTenantId())
                                    .type(StockTypeEnum.one.getValue())
                                    .build()
                    );
                }
            }
        }
        backOutboundShelfAfterHandler(userId, username, cancelDo, outboundDo, outboundProductDoList, pickingDoList.stream().sorted(Comparator.comparing(OutboundPickingDo::getSubStockId)).collect(Collectors.toList()), cellIdMatchDo, productIdMatch);
        outboundCancelAfterHandler(outboundDo, userId, outboundProductIdList);
        return SingleResult.success();
    }

    @Override
    public void backOutboundShelfAfterHandler(Long userId, String username, OutboundCancelDo cancelDo, OrderOutboundDo outboundDo, List<OrderOutboundProductDo> outboundProductDoList, List<OutboundPickingDo> pickingDoList, Map<Long, StorageLocationVo> cellIdMatchDo, Map<Long, ProductDo> productIdMatch) {
        //修改出库单状态、日志及业务进度条
        outboundDo.setInterceptionFlag(Boolean.FALSE);
        outboundDo.setStatus(outboundDo.getOrderType().equals(1) ? NormalOutboundStatusEnum.seven.getValue() : TransferOutboundStatusEnum.six.getValue());
        outboundDo.setCancelTime(DateUtil.date());
        orderOutboundDao.updateById(outboundDo);
        outboundProductService.lambdaUpdate()
                .eq(OrderOutboundProductDo::getOutboundId, outboundDo.getId())
                .set(OrderOutboundProductDo::getOutboundQty, 0)
                .set(OrderOutboundProductDo::getOutboundBoxQty, 0)
                .update();
        operateRecordService.operateAddRecordAndValue(
                SysConstant.Order_Interception_Success,
                "",
                outboundDo.getId(),
                userId,
                username,
                OperateTypeEnum.OrderOutbound
        );
        List<ProgressDo> progressDoList = progressService.lambdaQuery()
                .eq(ProgressDo::getType, ProgressEnum.OUTBOUND.getTypeCode())
                .eq(ProgressDo::getBizNumber, outboundDo.getId())
                .list();
        if (CollUtil.isNotEmpty(progressDoList)) {
            boolean isUpdate = false;
            for (ProgressDo progressDo : progressDoList.stream().sorted(Comparator.comparing(ProgressDo::getSort)).collect(Collectors.toList())) {
                if (Objects.isNull(progressDo.getOperateTime())) {
                    progressService.lambdaUpdate()
                            .eq(ProgressDo::getId, progressDo.getId())
                            .set(ProgressDo::getBizType, "cancel")
                            .set(ProgressDo::getOperateBy, "customer")
                            .set(ProgressDo::getOperateTime, DateUtil.date())
                            .update();
                    isUpdate = true;
                }
                if (isUpdate) {
                    progressService.removeById(progressDo.getId());
                }
            }
        }
        //修改截单状态、完成时间
        this.lambdaUpdate()
                .eq(OutboundCancelDo::getId, cancelDo.getId())
                .set(OutboundCancelDo::getStatus, 1)
                .set(OutboundCancelDo::getFinishTime, DateUtil.date())
                .update();
        //待拣部分 将锁定的库存释放
        for (OutboundPickingDo pickingDo : pickingDoList.stream().sorted(Comparator.comparing(OutboundPickingDo::getSubStockId)).collect(Collectors.toList())) {
            productStockRackService.lambdaUpdate()
                    .eq(ProductStockRackDo::getId, pickingDo.getSubStockId())
                    .setSql("quantity = quantity + " + pickingDo.getQty())
                    .setSql("lock_quantity = lock_quantity - " + pickingDo.getQty())
                    .update();
            pickingService.removeById(pickingDo.getId());
            Optional<OrderOutboundProductDo> first = outboundProductDoList.stream()
                    .filter(item -> item.getId().equals(pickingDo.getOutboundProductId()))
                    .findFirst();
            if (first.isPresent()) {
                OrderOutboundProductDo outboundProductDo = first.get();
                //库存流水
                stockFlowingService.insertProForIn(
                        ProductFlowingInEt.builder()
                                .sku(StrUtil.isNotEmpty(outboundProductDo.getBoxTypeNumber()) ? outboundProductDo.getBoxTypeNumber() : productIdMatch.get(outboundProductDo.getProductId()).getSku())
                                .companyId(outboundDo.getCompanyId())
                                .warehouseId(outboundDo.getWhId())
                                .rackId(pickingDo.getRackId())
                                .stockAttribute(cellIdMatchDo.get(pickingDo.getRackId()).getMainType())
                                .bookQuantity(pickingDo.getQty())
                                .orderType(StockOrderTypeEnum.Cancel.getValue())
                                .noteNo(cancelDo.getBackNo())
                                .tenantId(outboundDo.getTenantId())
                                .type(StockTypeEnum.one.getValue())
                                .build()
                );
            }
        }
    }

    @Override
    public void outboundCancelAfterHandler(OrderOutboundDo outboundDo, Long userId, List<Long> outboundProductIdList) {
        Integer orderType = outboundDo.getOrderType();
        if (orderType == 1) {
            //处理波次信息
            Long waveId = outboundDo.getWaveId();
            OutboundWaveDo waveDo = Objects.isNull(waveId) ? null : waveDao.selectById(waveId);
            if (Objects.nonNull(waveDo)) {
                if (waveDo.getOrderNum() == 1) {
                    waveDao.deleteById(waveId);
                } else {
                    waveDo.setOrderNum(waveDo.getOrderNum() - 1);
                    if (waveDo.getOrderNum().equals(waveDo.getOutboundOrder())) {
                        waveDo.setOutbound(Boolean.TRUE);
                        waveDo.setOutboundTime(DateUtil.date());
                    }
                    List<Long> outboundIdList = orderOutboundDao.selectList(
                            Wrappers.<OrderOutboundDo>lambdaQuery()
                                    .eq(OrderOutboundDo::getWaveId, waveId)
                                    .ne(OrderOutboundDo::getId, outboundDo.getId())
                    ).stream().map(OrderOutboundDo::getId).collect(Collectors.toList());
                    List<OrderOutboundProductDo> outboundProductListByWave = outboundProductService.lambdaQuery()
                            .in(OrderOutboundProductDo::getOutboundId, outboundIdList)
                            .list();
                    Integer noRecheckNum = outboundProductListByWave.stream().map(item -> item.getOutboundQty() - item.getRecheckQty()).reduce(0, Integer::sum);
                    if (noRecheckNum == 0) {
                        waveDo.setRecheck(Boolean.TRUE);
                        waveDo.setRecheckTime(DateUtil.date());
                    }
                    Integer noSecondarySortingNum = outboundProductListByWave.stream().map(item -> item.getOutboundQty() - item.getSecondarySortingQty()).reduce(0, Integer::sum);
                    if (noSecondarySortingNum == 0) {
                        waveDo.setSecondarySorting(Boolean.TRUE);
                        waveDo.setSecondarySortingTime(DateUtil.date());
                    }
                    waveDo.setShouldPickingQty(waveDo.getShouldPickingQty() - outboundDo.getTotalNum());
                    waveDo.setPickingQty(outboundProductListByWave.stream().map(OrderOutboundProductDo::getOutboundQty).reduce(0, Integer::sum));
                    waveDo.setWaveVarietyType(outboundProductListByWave.size() > 1 ? 2 : 1);
                    waveDao.updateById(waveDo);
                }
                //重置出库单波次
                orderOutboundDao.update(null, Wrappers.<OrderOutboundDo>lambdaUpdate().eq(OrderOutboundDo::getId, outboundDo.getId()).set(OrderOutboundDo::getWaveId, null));
                //重置拣货状态
                pickingService.lambdaUpdate()
                        .eq(OutboundPickingDo::getOrderType, OutboundOrderKind.outbound.getValue())
                        .in(OutboundPickingDo::getOutboundProductId, outboundProductIdList)
                        .set(OutboundPickingDo::getPickFlag, Boolean.FALSE)
                        .update();
                //重置二次分拣、复核数量
                outboundProductService.lambdaUpdate()
                        .in(OrderOutboundProductDo::getId, outboundProductIdList)
                        .set(OrderOutboundProductDo::getRecheckQty, 0)
                        .set(OrderOutboundProductDo::getSecondarySortingQty, 0)
                        .update();
            }
        }
        if (Objects.equals(outboundDo.getOrderOrigin(), OutboundOriginEnum.three.getValue()) && StringUtils.isNotEmpty(outboundDo.getPlatformOrderNo())) {
            //取消平台订单
            PlatOrderCancelConsumerReq req = new PlatOrderCancelConsumerReq(outboundDo.getPlatformOrderNo(), outboundDo.getId(), userId, outboundDo.getCompanyId());
            rabbitTemplate.convertAndSend(RabbitQueueConstant.PlatformOrder_Cancel, JSON.toJSONString(req));
        }
    }

    @Override
    public SingleResult backAllocateShelf(BackAllocateShelfBo bo, Long userId, String username) {
        Long cancelId = bo.getId();
        OutboundCancelDo cancelDo = this.getById(cancelId);
        if (Objects.isNull(cancelDo)) {
            throw new BizException(SysConstant.ORDER_CANCEL_NOT_EXIST);
        }
        if (cancelDo.getStatus() != 0) {
            throw new BizException(SysConstant.BACK_ORDER_SHELF_STATUS_EXCEPTION);
        }
        AllocatePlanDo planDo = allocatePlanDao.selectById(bo.getBatchId());
        if (Objects.isNull(planDo)) {
            throw new BizException(SysConstant.AllocateBatchPlan_Not_Exist);
        }
        operateRecordService.operateAddRecordAndValue(
                SysConstant.BACK_ORDER_SHELF,
                "",
                planDo.getId(),
                userId,
                username,
                OperateTypeEnum.AllocatePlan
        );
        List<AllocatePlanItemDo> planItemDoList = allocatePlanItemService.lambdaQuery()
                .eq(AllocatePlanItemDo::getOrderId, planDo.getId())
                .list();
        List<Long> planItemIdList = planItemDoList.stream().map(AllocatePlanItemDo::getId).collect(Collectors.toList());
        //查询拣货信息
        List<OutboundPickingDo> pickingDoList = pickingService.lambdaQuery()
                .eq(OutboundPickingDo::getOrderType, OutboundOrderKind.allocate.getValue())
                .in(OutboundPickingDo::getOutboundProductId, planItemIdList)
                .list();
        //查询库位信息 用于匹配库存属性
        StorageLocationQueryBo slBo = new StorageLocationQueryBo();
        slBo.setTenantId(planDo.getTenantId());
        slBo.setPageSize(1000);
        Map<Long, StorageLocationVo> cellIdMatchDo = storageLocationService.getListPage(slBo).getData()
                .stream().collect(Collectors.toMap(StorageLocationVo::getId, val -> val));
        Map<Long, ProductDo> productIdMatch = productService.lambdaQuery()
                .in(ProductDo::getId, planItemDoList.stream().map(AllocatePlanItemDo::getProductId).collect(Collectors.toList()))
                .list().stream().collect(Collectors.toMap(ProductDo::getId, val -> val));
        if (CollUtil.isNotEmpty(bo.getDetailList())) {
            //校验上架数量
            boolean qtyNoMatch = bo.getDetailList().stream()
                    .anyMatch(item -> !item.getShouldShelfQty().equals(item.getCellDetailList().stream()
                            .map(BackAllocateShelfBo.ShelfCellItem::getQty).reduce(0, Integer::sum)));
            if (qtyNoMatch) {
                throw new BizException(SysConstant.Shelf_Qty_No_Match);
            }
            //批量查询库存库位信息
            Map<Long, ProductStockRackDo> stockRackDoMap = productStockRackService.lambdaQuery()
                    .in(ProductStockRackDo::getId, pickingDoList.stream().map(OutboundPickingDo::getSubStockId).collect(Collectors.toSet()))
                    .list().stream().collect(Collectors.toMap(ProductStockRackDo::getId, val -> val));
            //拣货部分 将锁定的库存释放，复制库存子表记录做上架，保证库龄不改变
            //完成拣货的数据进行上架
            for (BackAllocateShelfBo.BackShelfItem shelfItem : bo.getDetailList()) {
                //按产品id或箱类号分组
                List<Long> planItemIdListByGroup = planItemDoList.stream()
                        .filter(item -> item.getProductId().equals(shelfItem.getProductId()))
                        .map(AllocatePlanItemDo::getId)
                        .collect(Collectors.toList());
                //按sku或箱类号分组
                List<OutboundPickingDo> pickingDoListByGroup = pickingDoList.stream()
                        .filter(item -> item.getPickFlag() && planItemIdListByGroup.contains(item.getOutboundProductId()))
                        .sorted(Comparator.comparing(OutboundPickingDo::getSubStockId))
                        .collect(Collectors.toList());
                for (int i = 0, j = 0; i < shelfItem.getCellDetailList().size(); i++) {
                    BackAllocateShelfBo.ShelfCellItem cellItem = shelfItem.getCellDetailList().get(i);
                    //需要上架数量
                    AtomicInteger needShelfQty = new AtomicInteger(cellItem.getQty());
                    while (j < pickingDoListByGroup.size()) {
                        if (needShelfQty.get() == 0) {
                            break;
                        }
                        OutboundPickingDo pickingDo = pickingDoListByGroup.get(j);
                        //拣货数量
                        Integer pickingQty = pickingDo.getQty();
                        if (pickingQty == 0) {
                            j++;
                            continue;
                        }
                        ProductStockRackDo stockRackDo = stockRackDoMap.get(pickingDo.getSubStockId());
                        int qty;
                        if (pickingQty <= needShelfQty.get()) {
                            j++;
                            qty = pickingQty;
                            pickingDo.setQty(0);
                            needShelfQty.set(needShelfQty.get() - pickingQty);
                            pickingService.removeById(pickingDo.getId());
                        } else {
                            qty = needShelfQty.get();
                            pickingDo.setQty(pickingQty - needShelfQty.get());
                            needShelfQty.set(0);
                        }
                        if (stockRackDo.getReceivedQty().equals(qty)) {
                            //库存数量等于上架数量，直接修改库位上架
                            productStockRackService.lambdaUpdate()
                                    .eq(ProductStockRackDo::getId, pickingDo.getSubStockId())
                                    .setSql("quantity = quantity + " + pickingDo.getQty())
                                    .setSql("lock_quantity = lock_quantity - " + pickingDo.getQty())
                                    .set(ProductStockRackDo::getRackId, cellItem.getRackId())
                                    .set(ProductStockRackDo::getStockAttribute, cellIdMatchDo.get(cellItem.getRackId()).getMainType())
                                    .update();
                        } else {
                            //库存数量大于上架数量，需要复制一条新的库位记录（在创建时间不改变的情况下）
//                            ProductStockRackDo newStockRackDo = new ProductStockRackDo();
//                            BeanUtils.copyProperties(stockRackDo, newStockRackDo);
                            ProductStockRackDo newStockRackDo = BeanConvert.INSTANCE.copyProductStockRackDo(stockRackDo);
                            newStockRackDo.setId(null);
                            newStockRackDo.setReceivedQty(qty);
                            newStockRackDo.setQuantity(qty);
                            newStockRackDo.setLockQuantity(0);
                            newStockRackDo.setRackId(cellItem.getRackId());
                            newStockRackDo.setStockAttribute(cellIdMatchDo.get(cellItem.getRackId()).getMainType());
                            productStockRackService.save(newStockRackDo);
                            productStockRackService.lambdaUpdate()
                                    .eq(ProductStockRackDo::getId, pickingDo.getSubStockId())
                                    .setSql("received_qty = received_qty - " + qty)
                                    .setSql("lock_quantity = lock_quantity - " + pickingDo.getQty())
                                    .set(ProductStockRackDo::getRackId, cellItem.getRackId())
                                    .set(ProductStockRackDo::getStockAttribute, cellIdMatchDo.get(cellItem.getRackId()).getMainType())
                                    .update();
                            stockRackDo.setReceivedQty(stockRackDo.getReceivedQty() - qty);
                        }
                    }
                    //库存流水
                    stockFlowingService.insertProForIn(
                            ProductFlowingInEt.builder()
                                    .sku(productIdMatch.get(shelfItem.getProductId()).getSku())
                                    .companyId(planDo.getCompanyId())
                                    .warehouseId(planDo.getDeliveryWhId())
                                    .rackId(cellItem.getRackId())
                                    .stockAttribute(cellIdMatchDo.get(cellItem.getRackId()).getMainType())
                                    .bookQuantity(cellItem.getQty())
                                    .orderType(StockOrderTypeEnum.Allocate.getValue())
                                    .noteNo(cancelDo.getBackNo())
                                    .tenantId(cancelDo.getTenantId())
                                    .type(StockTypeEnum.one.getValue())
                                    .build()
                    );
                }
            }
        }
        //释放待拣部分的库存
        backAllocateShelfAfterHandler(userId, username, cancelDo, planDo, planItemDoList, pickingDoList.stream().filter(item -> !item.getPickFlag()).collect(Collectors.toList()), cellIdMatchDo, productIdMatch);
        return SingleResult.success();
    }

    @Override
    public void backAllocateShelfAfterHandler(Long userId, String username, OutboundCancelDo cancelDo, AllocatePlanDo planDo, List<AllocatePlanItemDo> planItemDoList, List<OutboundPickingDo> pickingDoList, Map<Long, StorageLocationVo> cellIdMatchDo, Map<Long, ProductDo> productIdMatch) {
        //修改调拨状态、日志及业务进度条
        planDo.setInterceptionFlag(Boolean.FALSE);
        planDo.setStatus(AllocatePlanStatus.six.getValue());
        planDo.setCancelTime(DateUtil.date());
        allocatePlanDao.updateById(planDo);
        allocatePlanItemService.lambdaUpdate()
                .eq(AllocatePlanItemDo::getOrderId, planDo.getId())
                .set(AllocatePlanItemDo::getActualShipments, 0)
                .set(AllocatePlanItemDo::getLockQty, 0)
                .update();
        operateRecordService.operateAddRecordAndValue(
                SysConstant.Order_Interception_Success,
                "",
                planDo.getId(),
                userId,
                username,
                OperateTypeEnum.AllocatePlan
        );
        //修改截单状态、完成时间
        this.lambdaUpdate()
                .eq(OutboundCancelDo::getId, cancelDo.getId())
                .set(OutboundCancelDo::getStatus, 1)
                .set(OutboundCancelDo::getFinishTime, DateUtil.date())
                .update();
        //将锁定的库存释放
        for (OutboundPickingDo pickingDo : pickingDoList.stream().sorted(Comparator.comparing(OutboundPickingDo::getSubStockId)).collect(Collectors.toList())) {
            productStockRackService.lambdaUpdate()
                    .eq(ProductStockRackDo::getId, pickingDo.getSubStockId())
                    .setSql("quantity = quantity + " + pickingDo.getQty())
                    .setSql("lock_quantity = lock_quantity - " + pickingDo.getQty())
                    .update();
            pickingService.removeById(pickingDo.getId());
            Optional<AllocatePlanItemDo> first = planItemDoList.stream()
                    .filter(item -> item.getId().equals(pickingDo.getOutboundProductId()))
                    .findFirst();
            if (first.isPresent()) {
                AllocatePlanItemDo planItemDo = first.get();
                //库存流水
                stockFlowingService.insertProForIn(
                        ProductFlowingInEt.builder()
                                .sku(productIdMatch.get(planItemDo.getProductId()).getSku())
                                .companyId(planDo.getCompanyId())
                                .warehouseId(planDo.getDeliveryWhId())
                                .rackId(pickingDo.getRackId())
                                .stockAttribute(cellIdMatchDo.get(pickingDo.getRackId()).getMainType())
                                .bookQuantity(pickingDo.getQty())
                                .orderType(StockOrderTypeEnum.Allocate.getValue())
                                .noteNo(cancelDo.getBackNo())
                                .tenantId(planDo.getTenantId())
                                .type(StockTypeEnum.one.getValue())
                                .build()
                );
            }
        }
    }

    @Override
    public BackOutboundShelfVo getBackOutboundDetail(Long id) {
        OutboundCancelDo cancelDo = this.getById(id);
        if (Objects.isNull(cancelDo)) {
            throw new BizException(SysConstant.ORDER_CANCEL_NOT_EXIST);
        }
        BackOutboundShelfVo dto = new BackOutboundShelfVo();
        dto.setId(cancelDo.getId());
        OrderOutboundDo outboundDo = orderOutboundDao.selectById(cancelDo.getOutboundId());
        dto.setBackNo(cancelDo.getBackNo());
        dto.setOutboundNo(outboundDo.getOutboundNo());
        dto.setOutboundId(outboundDo.getId());
        dto.setDataType(
                outboundDo.getOrderType().equals(OutboundTypeEnum.one.getValue())
                        ? 1
                        : outboundDo.getOutboundMode().equals(1) ? 1 : 2
        );
        dto.setTrackingNumber(outboundDo.getTrackingNumber());
        LogisticsChannelDo logisticsChannelDo = logisticsChannelService.getById(outboundDo.getLogisticsChannelId());
        if (Objects.nonNull(logisticsChannelDo)) {
            dto.setLogisticsChannel(logisticsChannelDo.getChannelName() + "(" + logisticsChannelDo.getChannelCode() + ")");
        }
        CompanyDo companyDo = companyService.getById(outboundDo.getCompanyId());
        if (Objects.nonNull(companyDo)) {
            dto.setCompany(companyDo.getName() + "(" + companyDo.getCompanyCode() + ")");
        }
        Integer dataType = dto.getDataType();
        List<OrderOutboundProductDo> outboundProductDoList;
        Map<Long, ProductDo> productIdMatch = new HashMap<>();
        Map<String, OrderBoxDo> boxMatch = new HashMap<>();
        if (dataType.equals(1)) {
            outboundProductDoList = outboundProductService.lambdaQuery()
                    .eq(OrderOutboundProductDo::getOutboundId, outboundDo.getId())
                    .gt(OrderOutboundProductDo::getOutboundQty, 0)
                    .list();
            if (CollUtil.isNotEmpty(outboundProductDoList)) {
                productIdMatch = productService.lambdaQuery()
                        .in(
                                ProductDo::getId,
                                outboundProductDoList.stream()
                                        .map(OrderOutboundProductDo::getProductId)
                                        .collect(Collectors.toList())
                        )
                        .list().stream().collect(Collectors.toMap(ProductDo::getId, val -> val));
            }
        } else {
            outboundProductDoList = outboundProductService.lambdaQuery()
                    .eq(OrderOutboundProductDo::getOutboundId, outboundDo.getId())
                    .gt(OrderOutboundProductDo::getOutboundBoxQty, 0)
                    .list();
            outboundProductDoList = outboundProductDoList.stream()
                    .filter(distinctByKey(OrderOutboundProductDo::getBoxTypeNumber))
                    .collect(Collectors.toList());
//            outboundProductDoList = outboundProductService.lambdaQuery()
//                    .eq(OrderOutboundProductDo::getOutboundId, id)
////                    .apply("outbound_box_qty < forecast_box_qty")
//                    .list();
//            outboundProductDoList = outboundProductDoList.stream().collect(Collectors.toMap(OrderOutboundProductDo::getBoxTypeNumber, val -> val,(o1,o2)->{
//                o1.setOutboundBoxQty(o1.getOutboundBoxQty() + o2.getOutboundBoxQty());
//                return o1;
//            })).values().stream().filter(item -> item.getOutboundBoxQty() < item.getForecastBoxQty()).collect(Collectors.toList());
            if (CollUtil.isNotEmpty(outboundProductDoList)) {
                boxMatch = boxService.lambdaQuery()
                        .in(OrderBoxDo::getBoxTypeNumber,
                                outboundProductDoList.stream()
                                        .map(OrderOutboundProductDo::getBoxTypeNumber)
                                        .collect(Collectors.toSet()))
                        .groupBy(OrderBoxDo::getBoxTypeNumber)
                        .list().stream().collect(Collectors.toMap(OrderBoxDo::getBoxTypeNumber, val -> val, (o1, o2) -> o2));
            }
        }
        if (CollUtil.isEmpty(outboundProductDoList)) {
            return dto;
        }
        List<OutboundPickingDo> pickingDoList = pickingService.lambdaQuery()
                .eq(OutboundPickingDo::getOrderType, OutboundOrderKind.outbound.getValue())
                .in(OutboundPickingDo::getOutboundProductId, outboundProductDoList.stream()
                        .map(OrderOutboundProductDo::getId).collect(Collectors.toList()))
                .eq(OutboundPickingDo::getPickFlag, Boolean.TRUE)
                .list();
        if (CollUtil.isEmpty(pickingDoList)) {
            return dto;
        }
        Map<Long, String> rackIdMatchCode = storageLocationService.lambdaQuery()
                .in(StorageLocationDo::getId, pickingDoList.stream()
                        .map(OutboundPickingDo::getRackId)
                        .collect(Collectors.toSet()))
                .list().stream().collect(Collectors.toMap(StorageLocationDo::getId, StorageLocationDo::getCode));
        for (OrderOutboundProductDo outboundProductDo : outboundProductDoList) {
            BackOutboundShelfVo.ProductDetail productDetail = new BackOutboundShelfVo.ProductDetail();
            if (dataType.equals(1)) {
                productDetail.setProductId(outboundProductDo.getProductId());
                productDetail.setSkuOrBoxTypeNumber(productIdMatch.get(outboundProductDo.getProductId()).getSku());
                productDetail.setProductName(productIdMatch.get(outboundProductDo.getProductId()).getProductName());
                productDetail.setShouldShelfQty(outboundProductDo.getOutboundQty());
            } else {
                productDetail.setSkuOrBoxTypeNumber(outboundProductDo.getBoxTypeNumber());
                productDetail.setCustomBoxBarcode(boxMatch.get(outboundProductDo.getBoxTypeNumber()).getCustomizeBarcode());
                productDetail.setShouldShelfQty(outboundProductDo.getOutboundBoxQty());
            }
            List<OutboundPickingDo> pickingDos = pickingDoList.stream()
                    .filter(item -> item.getOutboundProductId().equals(outboundProductDo.getId()))
                    .collect(Collectors.toList());
            List<BackOutboundShelfVo.CellDetail> cellDetailList = JSONUtil.toList(JSONUtil.toJsonStr(pickingDos), BackOutboundShelfVo.CellDetail.class);
            cellDetailList.forEach(item -> item.setCellCode(rackIdMatchCode.get(item.getRackId())));
            productDetail.setCellDetailList(cellDetailList);
            dto.getDetailList().add(productDetail);
        }
        return dto;
    }

    @Override
    public BackAllocateShelfVo getBackAllocateDetail(Long id) {
        OutboundCancelDo cancelDo = this.getById(id);
        if (Objects.isNull(cancelDo)) {
            throw new BizException(SysConstant.ORDER_CANCEL_NOT_EXIST);
        }
        BackAllocateShelfVo dto = new BackAllocateShelfVo();
        dto.setId(cancelDo.getId());
        AllocatePlanDo planDo = allocatePlanDao.selectById(cancelDo.getOutboundId());
        dto.setBackNo(cancelDo.getBackNo());
        dto.setPlanNo(planDo.getPlanNo());
        dto.setBatchId(planDo.getId());
        dto.setTrackingNumber(planDo.getTrackingNumber());
        if (Objects.nonNull(planDo.getTrackingNumber())) {
            LogisticsChannelDo logisticsChannelDo = logisticsChannelService.getById(planDo.getLogisticsChannelId());
            if (Objects.nonNull(logisticsChannelDo)) {
                dto.setLogisticsChannel(logisticsChannelDo.getChannelName() + "(" + logisticsChannelDo.getChannelCode() + ")");
            }
        }
        CompanyDo companyDo = companyService.getById(planDo.getCompanyId());
        if (Objects.nonNull(companyDo)) {
            dto.setCompany(companyDo.getName() + "(" + companyDo.getCompanyCode() + ")");
        }
        List<AllocatePlanItemDo> planItemDoList = allocatePlanItemService.lambdaQuery()
                .eq(AllocatePlanItemDo::getOrderId, planDo.getId())
                .gt(AllocatePlanItemDo::getActualShipments, 0)
                .list();
        if (CollUtil.isEmpty(planItemDoList)) {
            return dto;
        }
        Map<Long, ProductDo> productIdMatch = productService.lambdaQuery()
                .in(
                        ProductDo::getId,
                        planItemDoList.stream()
                                .map(AllocatePlanItemDo::getProductId)
                                .collect(Collectors.toList())
                )
                .list().stream().collect(Collectors.toMap(ProductDo::getId, val -> val));
        List<OutboundPickingDo> pickingDoList = pickingService.lambdaQuery()
                .eq(OutboundPickingDo::getOrderType, OutboundOrderKind.allocate.getValue())
                .in(OutboundPickingDo::getOutboundProductId, planItemDoList.stream()
                        .map(AllocatePlanItemDo::getId).collect(Collectors.toList()))
                .eq(OutboundPickingDo::getPickFlag, Boolean.TRUE)
                .list();
        if (CollUtil.isEmpty(pickingDoList)) {
            return dto;
        }
        Map<Long, String> rackIdMatchCode = storageLocationService.lambdaQuery()
                .in(StorageLocationDo::getId, pickingDoList.stream()
                        .map(OutboundPickingDo::getRackId)
                        .collect(Collectors.toSet()))
                .list().stream().collect(Collectors.toMap(StorageLocationDo::getId, StorageLocationDo::getCode));
        for (AllocatePlanItemDo planItemDo : planItemDoList) {
            BackAllocateShelfVo.ProductDetail productDetail = new BackAllocateShelfVo.ProductDetail();
            productDetail.setProductId(planItemDo.getProductId());
            productDetail.setSku(productIdMatch.get(planItemDo.getProductId()).getSku());
            productDetail.setProductName(productIdMatch.get(planItemDo.getProductId()).getProductName());
            productDetail.setShouldShelfQty(planItemDo.getActualShipments());
            List<OutboundPickingDo> pickingDos = pickingDoList.stream()
                    .filter(item -> item.getOutboundProductId().equals(planItemDo.getId()))
                    .collect(Collectors.toList());
            List<BackAllocateShelfVo.CellDetail> cellDetailList = JSONUtil.toList(JSONUtil.toJsonStr(pickingDos), BackAllocateShelfVo.CellDetail.class);
            cellDetailList.forEach(item -> item.setCellCode(rackIdMatchCode.get(item.getRackId())));
            productDetail.setCellDetailList(cellDetailList);
            dto.getDetailList().add(productDetail);
        }
        return dto;
    }

    @Override
    public SingleResult listPageStatusSta(OutboundCancelQueryBo bo) {
        bo.setStatus(null);
        Map<Integer, List<OutboundCancelVo>> dataByStatus = baseMapper.getListPage(bo).stream()
                .collect(Collectors.groupingBy(OutboundCancelVo::getStatus));
        HashMap<Integer, Integer> map = new HashMap<>(8);
        for (int i = 0; i < 3; i++) {
            map.put(i, dataByStatus.getOrDefault(i, Collections.emptyList()).size());
        }
        return SingleResult.success(map);
    }

    @Override
    public SingleResult allocateListPageStatusSta(AllocateCancelQueryBo bo) {
        bo.setStatus(null);
        Map<Integer, List<AllocateCancelVo>> dataByStatus = baseMapper.getAllocateListPage(bo).stream().collect(Collectors.groupingBy(AllocateCancelVo::getStatus));
        HashMap<Integer, Integer> map = new HashMap<>(8);
        for (int i = 0; i < 3; i++) {
            map.put(i, dataByStatus.getOrDefault(i, Collections.emptyList()).size());
        }
        return SingleResult.success(map);
    }

    @Override
    @Transactional(propagation = Propagation.NOT_SUPPORTED)
    public CommonBatchOperationVo batchBackOutboundShelf(List<Long> ids, Long userId, String username) {
        CommonBatchOperationVo vo = new CommonBatchOperationVo();
        if (ArrayUtil.isEmpty(ids)) {
            return vo;
        }
        vo.setTotal(ids.size());
        Map<Long, OutboundCancelDo> cancelIdMatchDo = this.listByIds(ids).stream().collect(Collectors.toMap(OutboundCancelDo::getId, item -> item));
        if (CollUtil.isEmpty(cancelIdMatchDo)) {
            return vo;
        }
        List<Long> outIdList = cancelIdMatchDo.values().stream().map(OutboundCancelDo::getOutboundId).collect(Collectors.toList());
        if (CollUtil.isEmpty(outIdList)) {
            vo.setFailed(vo.getTotal());
            HashMap<String, Object> map = new HashMap<>(4);
            map.put("code", "All");
            map.put("errorMsg", StringsUtil.createI18Message(SysConstant.Outbound_Order_Does_Not_Exist));
            vo.getFailedList().add(map);
            return vo;
        }
        Map<Long, OrderOutboundDo> outIdMatchOrder = orderOutboundDao.selectBatchIds(outIdList).stream().collect(Collectors.toMap(OrderOutboundDo::getId, val -> val));
        if (CollUtil.isEmpty(outIdMatchOrder)) {
            vo.setFailed(vo.getTotal());
            HashMap<String, Object> map = new HashMap<>(4);
            map.put("code", "All");
            map.put("errorMsg", StringsUtil.createI18Message(SysConstant.Outbound_Order_Does_Not_Exist));
            vo.getFailedList().add(map);
            return vo;
        }
        Map<Long, List<OrderOutboundProductDo>> outIdMatchProducts = outboundProductService.lambdaQuery()
                .in(OrderOutboundProductDo::getOutboundId, outIdMatchOrder.keySet())
                .list().stream().collect(Collectors.groupingBy(OrderOutboundProductDo::getOutboundId));
        Map<Long, List<OutboundPickingDo>> outProductIdMatchPickDos = pickingService.lambdaQuery()
                .eq(OutboundPickingDo::getOrderType, OutboundOrderKind.outbound.getValue())
                .in(OutboundPickingDo::getOutboundProductId, outIdMatchProducts.values().stream().flatMap(Collection::stream).map(OrderOutboundProductDo::getId).collect(Collectors.toList()))
                .list().stream().collect(Collectors.groupingBy(OutboundPickingDo::getOutboundProductId));
        //查询库位信息 用于匹配库存属性
        StorageLocationQueryBo slBo = new StorageLocationQueryBo();
        slBo.setTenantId(cancelIdMatchDo.values().iterator().next().getTenantId());
        slBo.setPageSize(1000);
        Map<Long, StorageLocationVo> cellIdMatchDo = storageLocationService.getListPage(slBo).getData()
                .stream().collect(Collectors.toMap(StorageLocationVo::getId, val -> val));
        Map<Long, ProductDo> productIdMatch = productService.lambdaQuery()
                .in(ProductDo::getId, outIdMatchProducts.values().stream().flatMap(List::stream).map(OrderOutboundProductDo::getProductId).collect(Collectors.toList()))
                .list().stream().collect(Collectors.toMap(ProductDo::getId, val -> val));

        List<List<Long>> partition = ListUtil.partition(ids, 20);
        List<CompletableFuture<CommonBatchOperationVo>> futures = new ArrayList<>(partition.size());
        for (List<Long> batch : partition) {
            CompletableFuture<CommonBatchOperationVo> future = CompletableFuture.supplyAsync(() -> {
                CommonBatchOperationVo batchOperationVo = new CommonBatchOperationVo();
                for (Long cancelId : batch) {
                    Boolean executeStatus = transactionTemplate.execute(status -> {
                        try {
                            OutboundCancelDo cancelDo = cancelIdMatchDo.get(cancelId);
                            if (Objects.isNull(cancelDo)) {
                                vo.setFailed(vo.getFailed() + 1);
                                HashMap<String, Object> map = new HashMap<>(4);
                                map.put("code", cancelId);
                                map.put("errorMsg", StringsUtil.createI18Message(SysConstant.ORDER_CANCEL_NOT_EXIST));
                                vo.getFailedList().add(map);
                                return false;
                            }
                            if (cancelDo.getStatus() != 0) {
                                vo.setFailed(vo.getFailed() + 1);
                                HashMap<String, Object> map = new HashMap<>(4);
                                map.put("code", cancelDo.getBackNo());
                                map.put("errorMsg", StringsUtil.createI18Message(SysConstant.BACK_ORDER_SHELF_STATUS_EXCEPTION));
                                vo.getFailedList().add(map);
                                return false;
                            }
                            OrderOutboundDo outboundDo = outIdMatchOrder.get(cancelDo.getOutboundId());
                            if (Objects.isNull(outboundDo)) {
                                vo.setFailed(vo.getFailed() + 1);
                                HashMap<String, Object> map = new HashMap<>(4);
                                map.put("code", cancelDo.getBackNo());
                                map.put("errorMsg", StringsUtil.createI18Message(SysConstant.Outbound_Order_Does_Not_Exist));
                                vo.getFailedList().add(map);
                                return false;
                            }
                            operateRecordService.operateAddRecordAndValue(
                                    SysConstant.BACK_ORDER_SHELF,
                                    "",
                                    outboundDo.getId(),
                                    userId,
                                    username,
                                    OperateTypeEnum.OrderOutbound
                            );
                            List<OrderOutboundProductDo> outboundProductDoList = outIdMatchProducts.get(outboundDo.getId());
                            List<Long> outboundProductIdList = outboundProductDoList.stream().map(OrderOutboundProductDo::getId).collect(Collectors.toList());
                            //查询拣货信息
                            List<OutboundPickingDo> pickingDoList = new ArrayList<>();
                            for (Long outProductId : outboundProductIdList) {
                                List<OutboundPickingDo> pickingDos = outProductIdMatchPickDos.get(outProductId);
                                if (CollUtil.isNotEmpty(pickingDos)) {
                                    pickingDoList.addAll(pickingDos);
                                }
                            }
                            backOutboundShelfAfterHandler(userId, username, cancelDo, outboundDo, outboundProductDoList, pickingDoList, cellIdMatchDo, productIdMatch);
                            outboundCancelAfterHandler(outboundDo, userId, outboundProductIdList);
                            return true;
                        } catch (Exception e) {
                            status.setRollbackOnly();
                            return false;
                        }
                    });
                    if (Boolean.TRUE.equals(executeStatus)) {
                        batchOperationVo.setSuccess(batchOperationVo.getSuccess() + 1);
                    }
                }
                return batchOperationVo;
            }, threadPoolExecutor);
            futures.add(future);
        }
        //等待所有线程执行完毕,获取到所有结果
        CompletableFuture.anyOf(futures.toArray(new CompletableFuture[0])).thenAccept(commonBatchOperationVo -> {
            for (CompletableFuture<CommonBatchOperationVo> future : futures) {
                CommonBatchOperationVo join = future.join();
                vo.setSuccess(vo.getSuccess() + join.getSuccess());
                vo.setFailed(vo.getFailed() + join.getFailed());
                vo.getFailedList().addAll(join.getFailedList());
            }
        });
        return vo;
    }

    @Override
    public CommonBatchOperationVo batchMarkFail(List<Long> ids, OutboundOrderKind orderKind, Long userId, String username) {
        CommonBatchOperationVo vo = new CommonBatchOperationVo();
        if (ArrayUtil.isEmpty(ids)) {
            return vo;
        }
        vo.setTotal(ids.size());
        Map<Long, OutboundCancelDo> cancelIdMatchDo = this.listByIds(ids).stream().collect(Collectors.toMap(OutboundCancelDo::getId, item -> item));
        if (CollUtil.isEmpty(cancelIdMatchDo)) {
            return vo;
        }
        List<Long> outIdList = cancelIdMatchDo.values().stream().map(OutboundCancelDo::getOutboundId).collect(Collectors.toList());
        Map<Long, OrderOutboundDo> outIdMatchOutbound = null;
        Map<Long, AllocatePlanDo> outIdMatchAllocate = null;
        if (OutboundOrderKind.outbound.equals(orderKind)) {
            outIdMatchOutbound = orderOutboundDao.selectBatchIds(outIdList).stream().collect(Collectors.toMap(OrderOutboundDo::getId, val -> val));
            if (CollUtil.isEmpty(outIdMatchOutbound)) {
                vo.setFailed(vo.getTotal());
                HashMap<String, Object> map = new HashMap<>(4);
                map.put("code", "All");
                map.put("errorMsg", StringsUtil.createI18Message(SysConstant.Outbound_Order_Does_Not_Exist));
                vo.getFailedList().add(map);
                return vo;
            }
        } else if (OutboundOrderKind.allocate.equals(orderKind)) {
            outIdMatchAllocate = allocatePlanDao.selectBatchIds(outIdList).stream().collect(Collectors.toMap(AllocatePlanDo::getId, val -> val));
            if (CollUtil.isEmpty(outIdMatchAllocate)) {
                vo.setFailed(vo.getTotal());
                HashMap<String, Object> map = new HashMap<>(4);
                map.put("code", "All");
                map.put("errorMsg", StringsUtil.createI18Message(SysConstant.AllocateBatchPlan_Not_Exist));
                vo.getFailedList().add(map);
                return vo;
            }
        }
        for (Long cancelId : ids) {
            OutboundCancelDo cancelDo = cancelIdMatchDo.get(cancelId);
            if (Objects.isNull(cancelDo)) {
                vo.setFailed(vo.getFailed() + 1);
                HashMap<String, Object> map = new HashMap<>(4);
                map.put("code", cancelId);
                map.put("errorMsg", StringsUtil.createI18Message(SysConstant.ORDER_CANCEL_NOT_EXIST));
                vo.getFailedList().add(map);
                continue;
            }
            if (OutboundOrderKind.outbound.equals(orderKind)) {
                OrderOutboundDo outboundDo = outIdMatchOutbound.get(cancelDo.getOutboundId());
                //修改出库单拦截状态及日志
                outboundDo.setInterceptionFlag(Boolean.FALSE);
                orderOutboundDao.updateById(outboundDo);
                operateRecordService.operateAddRecordAndValue(
                        SysConstant.Order_Interception_Fail,
                        "",
                        outboundDo.getId(),
                        userId,
                        username,
                        OperateTypeEnum.OrderOutbound
                );
            } else if (OutboundOrderKind.allocate.equals(orderKind)) {
                AllocatePlanDo planDo = outIdMatchAllocate.get(cancelDo.getOutboundId());
                //修改调拨拦截状态及日志
                planDo.setInterceptionFlag(Boolean.FALSE);
                allocatePlanDao.updateById(planDo);
                operateRecordService.operateAddRecordAndValue(
                        SysConstant.Order_Interception_Fail,
                        "",
                        planDo.getId(),
                        userId,
                        username,
                        OperateTypeEnum.AllocatePlan
                );
            }
            this.lambdaUpdate()
                    .eq(OutboundCancelDo::getId, cancelId)
                    .set(OutboundCancelDo::getStatus, 2)
                    .set(OutboundCancelDo::getFinishTime, DateUtil.date())
                    .update();
            vo.setSuccess(vo.getSuccess() + 1);
        }
        return vo;
    }

    @Override
    @Transactional(propagation = Propagation.NOT_SUPPORTED)
    public CommonBatchOperationVo batchBackAllocateShelf(List<Long> ids, Long userId, String username) {
        CommonBatchOperationVo vo = new CommonBatchOperationVo();
        if (ArrayUtil.isEmpty(ids)) {
            return vo;
        }
        vo.setTotal(ids.size());
        Map<Long, OutboundCancelDo> cancelIdMatchDo = this.listByIds(ids).stream().collect(Collectors.toMap(OutboundCancelDo::getId, item -> item));
        if (CollUtil.isEmpty(cancelIdMatchDo)) {
            return vo;
        }
        List<Long> outIdList = cancelIdMatchDo.values().stream().map(OutboundCancelDo::getOutboundId).collect(Collectors.toList());
        if (CollUtil.isEmpty(outIdList)) {
            vo.setFailed(vo.getTotal());
            HashMap<String, Object> map = new HashMap<>(4);
            map.put("code", "All");
            map.put("errorMsg", StringsUtil.createI18Message(SysConstant.AllocateBatchPlan_Not_Exist));
            vo.getFailedList().add(map);
            return vo;
        }
        Map<Long, AllocatePlanDo> outIdMatchOrder = allocatePlanDao.selectBatchIds(outIdList).stream().collect(Collectors.toMap(AllocatePlanDo::getId, val -> val));
        if (CollUtil.isEmpty(outIdMatchOrder)) {
            vo.setFailed(vo.getTotal());
            HashMap<String, Object> map = new HashMap<>(4);
            map.put("code", "All");
            map.put("errorMsg", StringsUtil.createI18Message(SysConstant.AllocateBatchPlan_Not_Exist));
            vo.getFailedList().add(map);
            return vo;
        }
        Map<Long, List<AllocatePlanItemDo>> outIdMatchProducts = allocatePlanItemService.lambdaQuery()
                .in(AllocatePlanItemDo::getOrderId, outIdMatchOrder.keySet())
                .list().stream().collect(Collectors.groupingBy(AllocatePlanItemDo::getOrderId));
        Map<Long, List<OutboundPickingDo>> outProductIdMatchPickDos = pickingService.lambdaQuery()
                .eq(OutboundPickingDo::getOrderType, OutboundOrderKind.allocate.getValue())
                .in(OutboundPickingDo::getOutboundProductId, outIdMatchProducts.values().stream().flatMap(Collection::stream).map(AllocatePlanItemDo::getId).collect(Collectors.toList()))
                .list().stream().collect(Collectors.groupingBy(OutboundPickingDo::getOutboundProductId));
        //查询库位信息 用于匹配库存属性
        StorageLocationQueryBo slBo = new StorageLocationQueryBo();
        slBo.setTenantId(cancelIdMatchDo.values().iterator().next().getTenantId());
        slBo.setPageSize(1000);
        Map<Long, StorageLocationVo> cellIdMatchDo = storageLocationService.getListPage(slBo).getData()
                .stream().collect(Collectors.toMap(StorageLocationVo::getId, val -> val));
        Map<Long, ProductDo> productIdMatch = productService.lambdaQuery()
                .in(ProductDo::getId, outIdMatchProducts.values().stream().flatMap(List::stream).map(AllocatePlanItemDo::getProductId).collect(Collectors.toList()))
                .list().stream().collect(Collectors.toMap(ProductDo::getId, val -> val));

        List<List<Long>> partition = ListUtil.partition(ids, 20);
        List<CompletableFuture<CommonBatchOperationVo>> futures = new ArrayList<>(partition.size());
        for (List<Long> batch : partition) {
            CompletableFuture<CommonBatchOperationVo> future = CompletableFuture.supplyAsync(() -> {
                CommonBatchOperationVo batchOperationVo = new CommonBatchOperationVo();
                for (Long cancelId : batch) {
                    Boolean executeStatus = transactionTemplate.execute(status -> {
                        try {
                            OutboundCancelDo cancelDo = cancelIdMatchDo.get(cancelId);
                            if (Objects.isNull(cancelDo)) {
                                vo.setFailed(vo.getFailed() + 1);
                                HashMap<String, Object> map = new HashMap<>(4);
                                map.put("code", cancelId);
                                map.put("errorMsg", StringsUtil.createI18Message(SysConstant.ORDER_CANCEL_NOT_EXIST));
                                vo.getFailedList().add(map);
                                return false;
                            }
                            if (cancelDo.getStatus() != 0) {
                                vo.setFailed(vo.getFailed() + 1);
                                HashMap<String, Object> map = new HashMap<>(4);
                                map.put("code", cancelDo.getBackNo());
                                map.put("errorMsg", StringsUtil.createI18Message(SysConstant.BACK_ORDER_SHELF_STATUS_EXCEPTION));
                                vo.getFailedList().add(map);
                                return false;
                            }
                            AllocatePlanDo planDo = outIdMatchOrder.get(cancelDo.getOutboundId());
                            if (Objects.isNull(planDo)) {
                                vo.setFailed(vo.getFailed() + 1);
                                HashMap<String, Object> map = new HashMap<>(4);
                                map.put("code", cancelDo.getBackNo());
                                map.put("errorMsg", StringsUtil.createI18Message(SysConstant.AllocateBatchPlan_Not_Exist));
                                vo.getFailedList().add(map);
                                return false;
                            }
                            operateRecordService.operateAddRecordAndValue(
                                    SysConstant.BACK_ORDER_SHELF,
                                    "",
                                    planDo.getId(),
                                    userId,
                                    username,
                                    OperateTypeEnum.AllocatePlan
                            );
                            List<AllocatePlanItemDo> planItemDoList = outIdMatchProducts.get(planDo.getId());
                            List<Long> planItemIdList = planItemDoList.stream().map(AllocatePlanItemDo::getId).collect(Collectors.toList());
                            //查询拣货信息
                            List<OutboundPickingDo> pickingDoList = new ArrayList<>();
                            for (Long outProductId : planItemIdList) {
                                List<OutboundPickingDo> pickingDos = outProductIdMatchPickDos.get(outProductId);
                                if (CollUtil.isNotEmpty(pickingDos)) {
                                    pickingDoList.addAll(pickingDos);
                                }
                            }
                            backAllocateShelfAfterHandler(userId, username, cancelDo, planDo, planItemDoList, pickingDoList, cellIdMatchDo, productIdMatch);
                            return true;
                        } catch (Exception e) {
                            status.setRollbackOnly();
                            return false;
                        }
                    });
                    if (Boolean.TRUE.equals(executeStatus)) {
                        batchOperationVo.setSuccess(batchOperationVo.getSuccess() + 1);
                    }
                }
                return batchOperationVo;
            }, threadPoolExecutor);
            futures.add(future);
        }
        //等待所有线程执行完毕,获取到所有结果
        CompletableFuture.anyOf(futures.toArray(new CompletableFuture[0])).thenAccept(commonBatchOperationVo -> {
            for (CompletableFuture<CommonBatchOperationVo> future : futures) {
                CommonBatchOperationVo join = future.join();
                vo.setSuccess(vo.getSuccess() + join.getSuccess());
                vo.setFailed(vo.getFailed() + join.getFailed());
                vo.getFailedList().addAll(join.getFailedList());
            }
        });
        return vo;
    }
}
